package be.nepherte.movmanager.client.ui.components;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;

/**
 * <p>
 * SortableTable is a type of FlexTable which allows sorting on its column.
 * Sorting is done completely on the client side. No server side call is made in
 * this table.
 * </p>
 * 
 * <p>
 * Current implementation of Sortable Table needs <code>Comparable</code> Object
 * in its column values to be able to sort them correctly.
 * </p>
 * 
 * <p>
 * The objects being set in the column values must implement the interface
 * <code>Comparable</code> and implement methods: <code>compareTo()</code> and
 * <code>toString()</code>
 * </p>
 * 
 * <p>
 * Copyright © 2010 Bart Verhoeven
 * </p>
 * 
 * <p>
 * This file is part of MovManager.
 * </p>
 * 
 * <p>
 * MovManager is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * </p>
 * 
 * <p>
 * MovManager is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * </p>
 * 
 * <p>
 * You should have received a copy of the GNU General Public License along with
 * MovManager. If not, see <a
 * href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses</a>.
 * </p>
 * 
 * @author Bart Verhoeven, Parvinder Thapar
 * @version 0.1
 */
public class SortableTable extends FlexTable implements Sortable, ClickHandler {
	/**Holds the current sorted column.*/
	private int sortColIndex = -1;
	/**Holds the current direction of sort: ascending or descending.*/
	private int sortDirection =	-1;
	/**The default image to show ascending order arrow.*/
	private final String sortAscImage = GWT.getModuleBaseURL() + "images/asc.gif";
	/**The default image to show descending order arrow.*/
	private final String sortDescImage = GWT.getModuleBaseURL() + "images/desc.gif";
	/**
	 * The default image to show the blank image. This is needed to paint the
	 * columns other than the one which is being sorted. should be same length
	 * and width as the ascending and descending images.
	 */
	private final String blankImage = GWT.getModuleBaseURL() + "images/blank.gif";
	/**Holds the data rows of the table.*/
	private List<RowData> tableRows = new ArrayList<RowData>();
	/**Holds the data for the column headers.*/
	private List<String> tableHeader = new ArrayList<String>();
	
	/**
	 * Creates a new Sortable Table.
	 */
	public SortableTable() {
		super();
		this.addClickHandler(this);
	}

	/**
	 * Adds a column header to the table.
	 *  
	 * @param	name
	 * 			the name you want to have as header of the column	
	 * @param	index
	 * 			the index of the column
	 */
	public void addColumnHeader(String name, int index) {
		tableHeader.add(index, name);
		this.renderTableHeader(name, index);
	}
	
	/**
	 * Sets the value in the specified cell. It will ignore every value you try
	 * to set in row 0, which is used as table header.
	 * 
	 * @param	rowIndex
	 *          the index of the row you want to add the value to
	 * @param	colIndex
	 *          the index of the column you want to add the value to
	 * @param	value
	 *          the value you want to set
	 */
	public void setValue(int rowIndex, int colIndex, Comparable<?> value) {
		// The rowIndex should begin with 1 as rowIndex 0 is for the header
		if (rowIndex == 0)
			return;
		
		// the row index is larger than the current number of rows in this table
		// add a new row
		if((rowIndex-1) >= this.tableRows.size() || null == tableRows.get(rowIndex-1))
			tableRows.add(rowIndex-1, new RowData());
		
		// Set the value in the correct row and column
		RowData rowData = this.tableRows.get(rowIndex-1); 
		rowData.addColumnValue(colIndex, value);
		this.setHTML(rowIndex, colIndex, "" + value.toString() + "");
	}
	

	@Override
	@SuppressWarnings("unchecked")
	public void sort(int columnIndex){
		// Set the sorted column index throughout all rows 
		this.setSortColIndex(columnIndex);
		
		// sort
		Collections.sort(this.tableRows);
		
		// if different column clicked set ascending else flip sorting direction
		if (this.sortColIndex != columnIndex)
			this.sortDirection = SORT_ASC;
		else 
			this.sortDirection = (this.sortDirection == SORT_ASC)? SORT_DESC:SORT_ASC;
		
		this.sortColIndex = columnIndex;
		
		// render the table
		this.drawTable();
	}
	
	/**
	 * Specifies what happens upon clicking a column header: flip the sorting
	 * direction and sort the column.
	 */
	@Override
	public void onClick(ClickEvent event) {
		Cell clickedCell = getCellForEvent(event);
		if (clickedCell == null)
			return;
		
		int col = clickedCell.getCellIndex();
		
		if (clickedCell.getRowIndex() != 0)
			return;
		
		this.sort(col);
	}
	
	/**
	 * Renders the whole table.
	 */
	protected void drawTable() {
		this.displayTableHeader();
		this.displayTableBody();
	}
	
	/**
	 * Renders the table header.
	 */
	private void displayTableHeader() {
		int colIndex=0;
		
		for (String colHeader : tableHeader)
			this.renderTableHeader(colHeader, colIndex++);
	}
	
	/**
	 * Renders the body or the remaining rows of the table except the header. 
	 * Check the sort direction and display the rows accordingly.
	 */
	private void displayTableBody(){
		if (this.sortDirection == SORT_ASC || this.sortDirection == -1) {
			// Ascending order or Default Display
			for (int rowIndex=0; rowIndex<tableRows.size(); rowIndex++) {
				RowData columns = tableRows.get(rowIndex);
				
				for (int colIndex=0; colIndex<columns.getColumnValues().size(); colIndex++) {
					Object value = columns.getColumnValue(colIndex);
					
					if (null != value)
						this.setHTML(rowIndex+1, colIndex, value.toString());
				}
			}
		} else {
			// Descending Order Display
			for (int rowIndex=tableRows.size()-1, rowNum = 1; rowIndex>=0; rowIndex--, rowNum++) {
				RowData columns = tableRows.get(rowIndex);
				
				for (int colIndex=0; colIndex<columns.getColumnValues().size(); colIndex++) {
					Object value = columns.getColumnValue(colIndex);
					
					if (null != value)
						this.setHTML(rowNum, colIndex, value.toString());
				}
			}
		}
	}
	
	/**
	 * Sets the index of the sorted column throughout all rows.
	 * 
	 * @param	sortIndex
	 * 			the index of the column to sort
	 */
	private void setSortColIndex(int sortIndex) {
		for (int rowIndex=0; rowIndex<tableRows.size(); rowIndex++) {
			RowData row = tableRows.get(rowIndex);
			row.setSortColIndex(sortIndex);
		}
	}
	
	/**
	 * Renders a particular column in the table header.
	 * 
	 * @param 	name
	 * 			the name you want to set for this column
	 * @param	index
	 * 			the index of the column you want to render
	 */
	private void renderTableHeader(String name, int index) {
		StringBuffer headerText = new StringBuffer();
		headerText.append(name);
		headerText.append("&nbsp;<img border='0' src=");
		
		// if the table is sorted on this column
		if (this.sortColIndex == index) {
			if (this.sortDirection == SORT_ASC)
				headerText.append("'" + this.sortAscImage + "' alt='Ascending' ");	
			else
				headerText.append("'" + this.sortDescImage + "' alt='Descending' ");
		} else {
			headerText.append("'" + this.blankImage + "'");
		}
		
		headerText.append("/>");
		this.setHTML(0, index, headerText.toString());
	}
}